using System;
using System.Linq;
using System.Collections.Generic;
using System.ServiceModel;

namespace Hvac
{
    [ServiceContract]
    public interface IZoneController
    {
        IEnumerable<Zone> Zones
        {
            [OperationContract]
            get;
        }

        [OperationContract]
        void SetZone(string name, double setPointLow, double setPointHigh, SatisfyOption satisfySetting);

        [OperationContract]
        Zone GetZone(string name);

        bool Enabled
        {
            [OperationContract]
            get;

            [OperationContract]
            set;
        }
    }

    /// <summary>
    /// Decides when to turn on/off heat/cool and tells dampers to move
    /// </summary>
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
    public class ZoneController : IZoneController
    {
        List<Zone> _zones = new List<Zone>();
        public IEnumerable<Zone> Zones { get { return _zones; } }

        readonly IHvacController hvac;

        public ZoneController(IHvacSystem hvacSystem)
        {
            hvac = hvacSystem.HvacController;

            foreach (Zone z in hvacSystem.Zones)
            {
                AddZone(z);
            }
        }

        public ZoneController(IHvacController hvacSystem)
        {
            hvac = hvacSystem;
        }

        public void AddZone(Zone zone)
        {
            _zones.Add(zone);
            zone.DemandChanged += new EventHandler<DemandChangedEventArgs>(zone_DemandChanged);
            double d = zone.Temperature;
        }

        public void SetZone(string name, double setPointLow, double setPointHigh, SatisfyOption satisfySetting)
        {
            foreach(Zone z in Zones)
            {
                if (z.Name == name)
                {
                    z.SetPointLow = setPointLow;
                    z.SetPointHigh = setPointHigh;
                    z.SatisfySetting = satisfySetting;
                }
            }
        }

        public Zone GetZone(string name)
        {
            return Zones.First(t => t.Name == name);
        }

        public bool Enabled
        {
            get { return hvac.Enabled; }
            set 
            {
                if (value != hvac.Enabled)
                {
                    if (!value)
                    {
                        foreach (Zone z in Zones)
                        {
                            z.AirVolumeController.SetVolume(1);
                        }
                        hvac.Demand(0);
                    }

                    hvac.Enabled = value;
                }
            }
        }

        // TODO Fix bug that prevents a mustSatisfy zone from being satisfied if it requests the oposite mode and it's temperature isn't reevaluated
        void zone_DemandChanged(object sender, DemandChangedEventArgs e)
        {
            Zone zone = sender as Zone;

            if (!Enabled)
                return;

            if (!hvac.IsRunning)
            {
                if (e.SatisfyStatus == SatisfyOption.MustSatisfy)
                {
                    // TODO do we need to protect the HVAC unit from quickly switching between modes?
                    // one side effect to this code is that the first MustSatify will trigger a mode change even if all 
                    // other zones are requesting a ShouldSatisfy in the other direction.
                    if (zone.TemperatureError < 0) //requesting cool
                        hvac.Mode = -1;
                    else if (zone.TemperatureError > 0) //requesting heat
                        hvac.Mode = 1;
                }
                else
                {
                    // HVAC is not running and this is not a MustSatisfy zone
                    return;
                }
            }

            double demand = 0;
            SatisfyOption satisfyPriority = e.SatisfyStatus;

            // The zone with the largest temperatureError gets the largest proportion of conditioned air.
            // This variable represents the largest temperatureError and is used to determine the scale for other calling zones
            double largestTemperatureError = 0;

            // iterate all zones calling for the current hvac.Mode and calculate demand, highest priority of a calling zone, and largestTemperatureError
            foreach (Zone z in Zones.Where(t => t.TemperatureError * hvac.Mode > 0))
            {
                demand += z.TemperatureError;

                if ((int)z.SatisfySetting < (int)satisfyPriority)
                    satisfyPriority = z.SatisfySetting;

                // Update the largestTemperatureError. MaySatisfy zones do not update this value
                if (z.SatisfySetting != SatisfyOption.MaySatisfy && Math.Abs(z.TemperatureError) > Math.Abs(largestTemperatureError))
                    largestTemperatureError = z.TemperatureError;
            }

            // If only DoNotSatisfy || MaySatisfy zones are calling, then shut the hvac system down
            if (satisfyPriority == SatisfyOption.DoNotSatisfy || satisfyPriority == SatisfyOption.MaySatisfy)
                demand = 0;

            if (demand == 0)
            {
                // Open all dampers when hvac system is off
                foreach (Zone z in Zones)
                {
                    z.AirVolumeController.SetVolume(1);
                }
            }
            else
            {
                // Normalize
                double scale = 1 / largestTemperatureError;

                // Set damper positions
                foreach (Zone z in Zones)
                {
                    double position = z.TemperatureError * scale;
                    if (position < 0)
                        position = 0;
                    else if (position > 1)
                        position = 1;
                    z.AirVolumeController.SetVolume(position);
                }
            }

            hvac.Demand(demand);
        }
    }
}